package com.luo.gmall.index.service.impl;

import com.alibaba.fastjson.JSON;
import com.luo.gmall.common.bean.ResponseVo;
import com.luo.gmall.index.config.GmallCache;
import com.luo.gmall.index.feign.GmallPmsClient;
import com.luo.gmall.index.service.IndexService;
import com.luo.gmall.index.util.DistributeLock;
import com.luo.gmall.pms.entity.CategoryEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author Zoro
 * @date 2021年11月13
 */
@Service
@Slf4j
public class IndexServiceImpl implements IndexService {

    @Autowired
    private GmallPmsClient gmallPmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private DistributeLock distributeLock;
    private static final String REDIS_PREFIX = "index:category:";
    private static final String REDIS_TOP_PREFIX = "index:category:top";


    /**
     * 查询所有一级分类的列表数据
     */
    @Override
    public List<CategoryEntity> queryLevelOneCategories() {

        // 利用redis添加缓存
        // 1.根据parentId先去redis中查询子分类的数据
        String redisCache = redisTemplate.opsForValue().get(REDIS_TOP_PREFIX);
        // 2.判断查询出来的数据是否为空
        if (StringUtils.isNotBlank(redisCache)) {
            // 3.有数据，解析数据返回
            return JSON.parseArray(redisCache, CategoryEntity.class);
        }
        // 3.没有数据，远程调用获取数据，然后将数据存放到redis中存放90天
        List<CategoryEntity> entities = gmallPmsClient.queryTopCategoriesByParentId(0L).getData();
        // 数据为空也进行添加（解决缓存穿透）
        redisCache = JSON.toJSONString(entities);
        if (CollectionUtils.isEmpty(entities)) {
            // 数据为空也进行添加（解决缓存穿透）
            redisTemplate.opsForValue().set(REDIS_PREFIX, redisCache, 5, TimeUnit.MINUTES);
        } else {
            // 添加缓存随机时间避免数据同时过期，访问量激增引起缓存雪崩的
            redisTemplate.opsForValue().set(REDIS_PREFIX, redisCache, 90, TimeUnit.DAYS);
        }
        return entities;
    }

    /**
     * 查询所有一级分类的列表数据
     */
//    @Override
    @GmallCache(prefix = REDIS_TOP_PREFIX,timeout = 129600,random = 1440,lock = "index:lock2:")
    public List<CategoryEntity> queryLevelOneCategories2() {

        ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryTopCategoriesByParentId(0L);

        return listResponseVo.getData();
    }

    /**
     * 根据一级分类列表查询出二级和三级分类的列表数据（嵌套列表数据）
     *
     * @param parentId 一级分类id
     */
    @Override
    @GmallCache(prefix = REDIS_PREFIX, timeout = 129600, random = 1440, lock = "index:lock:")
    public List<CategoryEntity> queryListCategoryByParentId(Long parentId) {
        ResponseVo<List<CategoryEntity>> listResponseVo = gmallPmsClient.queryLevelTwoAndThreeCategoryListByParentId(parentId);
        return listResponseVo.getData();
    }


    public List<CategoryEntity> queryListCategoryByParentId2(Long parentId) {
        // 利用redis添加缓存
        // 1.根据parentId先去redis中查询子分类的数据
        String redisCache = redisTemplate.opsForValue().get(REDIS_PREFIX + parentId);
        // 2.判断查询出来的数据是否为空
        if (StringUtils.isNotBlank(redisCache)) {
            // 3.有数据，解析数据返回
            return JSON.parseArray(redisCache, CategoryEntity.class);
        }
        // 添加分布式锁解决因为一个热点key过期导致的缓存击穿问题
        RLock lock = redissonClient.getLock("index:category:hot:" + parentId);
        lock.lock();
        try {
            // 3.没有数据，远程调用获取数据，然后将数据存放到redis中存放90天
            List<CategoryEntity> entities = gmallPmsClient.queryLevelTwoAndThreeCategoryListByParentId(parentId).getData();
            // 数据为空也进行添加（解决缓存穿透）
            redisCache = JSON.toJSONString(entities);
            if (CollectionUtils.isEmpty(entities)) {
                redisTemplate.opsForValue().set(REDIS_PREFIX + parentId, redisCache, 5, TimeUnit.MINUTES);
            } else {
                // 添加缓存随机时间避免数据同时过期，访问量激增引起缓存雪崩的
                redisTemplate.opsForValue().set(REDIS_PREFIX + parentId, redisCache, 90, TimeUnit.DAYS);
            }
            return entities;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return null;
    }


    @Override
    public void testLock() {
        RLock lock = redissonClient.getLock("lock");
        // 加锁
        lock.lock();
        try {
            String num = redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(num)) {
                redisTemplate.opsForValue().set("num", "1");
            } else {
                redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));
            }
//            TimeUnit.SECONDS.sleep(100);
            testLock3();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }
    }

    @Override
    public void testReader() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("lock");
        lock.readLock().lock(10, TimeUnit.SECONDS);
    }

    @Override
    public void testWriter() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("lock");
        lock.writeLock().lock(10, TimeUnit.SECONDS);
    }

    @Override
    public void testLatch() {
        RCountDownLatch latch = redissonClient.getCountDownLatch("latch");
        latch.trySetCount(6);
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void testDown() {
        RCountDownLatch latch = redissonClient.getCountDownLatch("latch");
        latch.countDown();
    }

    private void testLock3() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        log.info("锁可重入");
        lock.unlock();
    }


    public void testLockManual() {
        String uuid = UUID.randomUUID().toString();
        // 加锁
        Boolean lock = distributeLock.lock("lock", uuid, 30);
        if (lock) {
            String num = redisTemplate.opsForValue().get("num");
            // 没有该值
            if (StringUtils.isBlank(num)) {
                redisTemplate.opsForValue().set("num", "1");
            } else {
                // 把redis中的num值+1
                redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));
            }
            // 重入锁
//            inLock(uuid);
            try {
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 解锁
            distributeLock.unLock("lock", uuid);
        }
    }


    public void inLock(String uuid) {
        // 加锁
        Boolean lock = distributeLock.lock("lock", uuid, 30);
        if (lock) {
            log.info("重入了锁");
            distributeLock.unLock("lock", uuid);
        }
    }


    public void testLock2() {
        String uuid = UUID.randomUUID().toString();
//        String num = redisTemplate.opsForValue().get("num");
//        if(StringUtils.isBlank(num)){
//            redisTemplate.opsForValue().set("num","1");
//        }
//        redisTemplate.opsForValue().set("num",String.valueOf(Integer.parseInt(num)+1));
        // 1.从redis中获取锁，setnx
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(lock)) {
            // 查询redis中num值
            String num = redisTemplate.opsForValue().get("num");
            // 没有该值
            if (StringUtils.isBlank(num)) {
                redisTemplate.opsForValue().set("num", "1");
            } else {
                // 把redis中的num值+1
                redisTemplate.opsForValue().set("num", String.valueOf(Integer.parseInt(num) + 1));
            }
            // 释放锁 del 判断和删除之间没有原子性
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del',KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            // 获取lock的值与uuid比较，如果相同删除lock，释放锁
            redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Collections.singletonList("lock"), uuid);
        } else {
            // 3.每隔1秒回调回调一次，再次尝试获取锁
            try {
                TimeUnit.SECONDS.sleep(1);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }


}
